﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Data.Common;
using Tilver.Configuration;
using Tilver.Configuration.Data;

namespace Tilver.Data
{
    /// <summary>
    /// 数据库访问工厂类
    /// </summary>
    public class DbDataFactory
    {
        /// <summary>
        /// 数据访问工厂锁
        /// </summary>
        private static object _providerLock = new object();

        /// <summary>
        /// 配置文件中的数据库访问配置信息
        /// </summary>
        private static Dictionary<string, DataBaseConfigData> _dataBaseConfigCache = new Dictionary<string, DataBaseConfigData>();
        /// <summary>
        /// 数据库访问类程序集集合
        /// </summary>
        private static Dictionary<DbProviderType, string> _providerNames = new Dictionary<DbProviderType, string>();  
        
        /// <summary>
        /// 数据库访问工厂
        /// </summary>
        private static Dictionary<DbProviderType, DbProviderFactory> _providerFactoies = new Dictionary<DbProviderType, DbProviderFactory>();


        static DbDataFactory()
        {
            //加载数据库访问类的程序集
            _providerNames.Add(DbProviderType.SQLServer, "System.Data.SqlClient");
            _providerNames.Add(DbProviderType.OleDb, "System.Data.OleDb");
            _providerNames.Add(DbProviderType.Odbc, "System.Data.Odbc");
            _providerNames.Add(DbProviderType.Oracle, "System.Data.OracleClient");
            _providerNames.Add(DbProviderType.MySql, "MySql.Data.MySqlClient");
            _providerNames.Add(DbProviderType.SQLite, "System.Data.SQLite");
            _providerNames.Add(DbProviderType.Firebird, "FirebirdSql.Data.Firebird");
            _providerNames.Add(DbProviderType.PostgreSql, "Npgsql");
            _providerNames.Add(DbProviderType.DB2, "IBM.Data.DB2.iSeries");
            _providerNames.Add(DbProviderType.Informix, "IBM.Data.Informix");
            _providerNames.Add(DbProviderType.SqlServerCe, "System.Data.SqlServerCe");
            //加载数据库配置信息
            _dataBaseConfigCache = GetCacheFromConfig();
        }


        /// <summary>
        /// 创建数据库访问实例
        /// </summary>
        /// <param name="connectionStringName"></param>
        /// <returns></returns>
        public static DbDataBase Create(string connectionStringName =null)
        {
            if (string.IsNullOrEmpty(connectionStringName))
            {
                var section = TilverSection.GetSection();
                if (((section == null) || (section.DataConfig == null)) || string.IsNullOrEmpty(section.DataConfig.DefaultDataBase))
                {
                    if ((_dataBaseConfigCache == null) || (_dataBaseConfigCache.Count != 1))
                    {
                        throw new ArgumentNullException("connectionStringName");
                    }
                    connectionStringName = _dataBaseConfigCache.Keys.ToList<string>()[0];
                }
                else
                {
                    connectionStringName = section.DataConfig.DefaultDataBase;
                }
            }
            if (!_dataBaseConfigCache.ContainsKey(connectionStringName))
            {
                throw new ArgumentException("配置文件中的Tilver.data.connectionStrings配置节中不包含此连接字符串名。", "connectionStringName");
            }
            return new DbDataBase(_dataBaseConfigCache[connectionStringName].ConnectionString, _dataBaseConfigCache[connectionStringName].ProviderType);
        }
        /// <summary>
        /// 创建数据库访问实例
        /// </summary>
        /// <param name="connectionString"></param>
        /// <param name="providerType"></param>
        /// <returns></returns>
        public static DbDataBase Create(string connectionString, DbProviderType providerType)
        {
            return new DbDataBase(connectionString, providerType);
        }

        #region 内部方法

        /// <summary>  
        /// 获取指定数据库类型对应的程序集名称  
        /// </summary>  
        /// <param name="providerType">数据库类型枚举</param>  
        /// <returns></returns>  
        public static string GetProviderName(DbProviderType providerType)
        {
            return _providerNames[providerType];
        }
        /// <summary>  
        /// 获取指定类型的数据库对应的DbProviderFactory  
        /// </summary>  
        /// <param name="providerType">数据库类型枚举</param>  
        /// <returns></returns>  
        public static DbProviderFactory GetDbProviderFactory(DbProviderType providerType)
        {
            //如果还没有加载，则加载该DbProviderFactory  
            if (!_providerFactoies.ContainsKey(providerType))
            {
                lock (_providerLock)
                {
                    if (!_providerFactoies.ContainsKey(providerType))
                    {
                        _providerFactoies.Add(providerType, ImportDbProviderFactory(providerType));
                    }
                }
            }
            return _providerFactoies[providerType];
        }
        /// <summary>  
        /// 加载指定数据库类型的DbProviderFactory  
        /// </summary>  
        /// <param name="providerType">数据库类型枚举</param>  
        /// <returns></returns>  
        private static DbProviderFactory ImportDbProviderFactory(DbProviderType providerType)  
        {  
            string providerName = _providerNames[providerType];  
            DbProviderFactory factory;  
            try  
            {  
                //从全局程序集中查找  
                factory = DbProviderFactories.GetFactory(providerName);  
            }  
            catch
            {  
                factory = null;  
            }
            return factory;  
        }
        /// <summary>
        /// 从配置文件中获取数据库配置信息
        /// </summary>
        /// <returns></returns>
        private static Dictionary<string, DataBaseConfigData> GetCacheFromConfig()
        {
            var ret = new Dictionary<string, DataBaseConfigData>();
            TilverSection section = TilverSection.GetSection();
            if (section != null && section.DataConfig != null)
            {
                // 加载自定义配置节中数据库配置
                foreach (ConnectionStringElement element in section.DataConfig.ConnectionStrings)
                {
                    if (ret.ContainsKey(element.Name))
                        throw new ArgumentException("配置文件中Tilver.data.connectionStrings配置节存在重复Name定义。");
                    else
                        ret.Add(element.Name, GetDataBaseConfigData(element));
                }
            }
            return ret;
        }
        /// <summary>
        /// 获取数据库配置数据
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        private static DataBaseConfigData GetDataBaseConfigData(ConnectionStringElement element)
        {
            DbProviderType provider = GetProviderType(element.ProviderName);
            return new DataBaseConfigData(element.Name, element.ConnectionString, provider);
        }
        /// <summary>
        /// 根据配置文件中的providerName获得DbProviderType
        /// </summary>
        /// <param name="providerName"></param>
        /// <returns></returns>
        private static DbProviderType GetProviderType(string providerName)
        {
            var ret = new DbProviderType();
            if (!_providerNames.ContainsValue(providerName))
            {
                throw new Exception("系统中没有定义此数据库访问类的程序集");
            }
            foreach (var n in _providerNames)
            {
                if (string.Equals(n.Value, providerName, StringComparison.CurrentCultureIgnoreCase))
                {
                    ret = n.Key;
                }
            }
            return ret;
        }
        #endregion

    }

    /// <summary>
    /// 数据库连接配置信息类
    /// </summary>
    [Serializable]
    internal class DataBaseConfigData
    {
        public DataBaseConfigData() { }
        public DataBaseConfigData(string name, string connectionString, DbProviderType providerType)
        {
            this.Name = name;
            this.ConnectionString = connectionString;
            this.ProviderType = providerType;
        }
        public string Name { get; set; }
        public string ConnectionString { get; set; }
        public DbProviderType ProviderType { get; set; }
    }
}
